package com.safewind.wx.util;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.util.Date;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.safewind.wx.bean.AccessToken;
import com.safewind.wx.bean.JsApiTicket;
import com.safewind.wx.bean.Menu;
import com.safewind.wx.bean.MessageTemplate;
import com.safewind.wx.bean.WebUser;
import com.safewind.wx.constant.WxConstant;

/**
 * 微信平台相关工具类
 */
public class WxUtil {
    private static final Logger logger = LoggerFactory.getLogger(WxUtil.class);

    private static AccessToken accessToken = null;
    private static JsApiTicket jsApiTicket = null;

    public synchronized static AccessToken getAccessToken() {
        if (accessToken == null || isExpired(accessToken)) {
            accessToken = WxUtil.getAccessTokenFromWX();
        }
        return accessToken;
    }

    public synchronized static JsApiTicket getJsApiTicket() {
        if (jsApiTicket == null || isExpired(jsApiTicket)) {
            jsApiTicket = WxUtil.getJsApiTicketFromWX();
        }
        return jsApiTicket;
    }

    /**
     * 每小时刷新一次
     * 
     * @param at
     * @return
     */
    private static boolean isExpired(AccessToken at) {
        if (new Date().getTime() / 1000 - at.getExpiresIn() > 3600) {
            return true;
        }
        return false;
    }

    /**
     * 每小时刷新一次
     * 
     * @param at
     * @return
     */
    private static boolean isExpired(JsApiTicket at) {
        if (new Date().getTime() / 1000 - at.getExpiresIn() > 3600) {
            return true;
        }
        return false;
    }

    /**
     * 获取access_token
     * 
     * @param appid
     *            凭证
     * @param appsecret
     *            密钥
     * @return
     * @throws Exception
     */
    private synchronized static AccessToken getAccessTokenFromWX() {
        AccessToken accessToken = null;

        String url = WxConstant.ACCESS_TOKEN_URL;
        url = url.replace("APPID", WxConstant.APP_ID).replace("APPSECRET", WxConstant.APP_SECRET);
        JSONObject jsonObject = HttpUtil.httpsRequest(url, "GET", null);
        // 如果请求成功
        if (null != jsonObject) {
            try {
                accessToken = new AccessToken();
                accessToken.setToken(jsonObject.getString("access_token"));
                accessToken.setExpiresIn(jsonObject.getInteger("expires_in"));
            } catch (Exception e) {
                accessToken = null;
                // 获取token失败
                logger.error("获取token失败 errcode:{} errmsg:{}", jsonObject.getInteger("errcode"),
                        jsonObject.getString("errmsg"));
            }
        }
        return accessToken;
    }

    /**
     * 获取jsApiTicket
     * 
     * @return
     * @throws Exception
     */
    private synchronized static JsApiTicket getJsApiTicketFromWX() {
        JsApiTicket jsApiTicket = null;

        String url = WxConstant.JS_API_TICKET_URL;
        url = url.replace("ACCESS_TOKEN", getAccessToken().getToken());
        JSONObject jsonObject = HttpUtil.httpsRequest(url, "GET", null);
        // 如果请求成功
        if (null != jsonObject) {
            try {
                jsApiTicket = new JsApiTicket();
                jsApiTicket.setTicket(jsonObject.getString("ticket"));
                jsApiTicket.setExpiresIn(jsonObject.getInteger("expires_in"));
            } catch (Exception e) {
                jsApiTicket = null;
                // 获取jsApiTicket失败
                logger.error("获取jsApiTicket失败 errcode:{} errmsg:{}", jsonObject.getInteger("errcode"),
                        jsonObject.getString("errmsg"));
            }
        }
        return jsApiTicket;
    }

    /**
     * 创建菜单
     *
     * @param menu
     *            菜单实例
     * @param accessToken
     *            有效的access_token
     * @return 0表示成功，其他值表示失败
     */
    public static int createMenu(Menu menu) {
        int result = 0;
        // 拼装创建菜单的url
        String url = WxConstant.MENU_CREATE_URL;
        url = url.replace("ACCESS_TOKEN", getAccessToken().getToken());
        // 将菜单对象转换成json字符串
        String jsonMenu = JSON.toJSONString(menu);
        // 调用接口创建菜单
        JSONObject jsonObject = HttpUtil.httpsRequest(url, "POST", jsonMenu);
        if (null != jsonObject) {
            if (0 != jsonObject.getInteger("errcode")) {
                result = jsonObject.getInteger("errcode");
                logger.error("创建菜单失败 errcode:{} errmsg:{}", jsonObject.getInteger("errcode"),
                        jsonObject.getString("errmsg"));
            }
        }
        return result;
    }

    /**
     * 获取用户信息
     * 
     * @param code
     *            用户同意网页授权凭证
     * @return
     * @throws Exception
     */
    public static WebUser getWebUser(String code) throws Exception {
        if (code == null) {
            throw new Exception();
        }
        String url = WxConstant.GET_WEB_ACCESSTOKEN_URL;
        url = url.replace("APPID", WxConstant.APP_ID).replace("SECRET", WxConstant.APP_SECRET).replace("CODE", code);
        JSONObject jsonObject = HttpUtil.httpsRequest(url, "GET", null);
        WebUser webUser = null;
        // 如果请求成功
        if (null != jsonObject) {
            try {
                webUser = new WebUser();
                webUser.setAccess_token(jsonObject.getString("access_token"));
                webUser.setExpires_in(jsonObject.getInteger("expires_in"));
                webUser.setRefresh_token(jsonObject.getString("refresh_token"));
                webUser.setOpenid(jsonObject.getString("openid"));
                webUser.setScope(jsonObject.getString("scope"));
            } catch (Exception e) {
                // 获取token失败
                logger.error("凭code获取用户信息失败 errcode:{} errmsg:{}", jsonObject.getInteger("errcode"),
                        jsonObject.getString("errmsg"));
                throw new Exception();
            }
        }
        return webUser;
    }

    /**
     * 
     * @param code
     * @return
     * @throws Exception
     */
    public static Map<String, String> getUserInfo(String openId) throws Exception {
        String url = WxConstant.USERINFO_URL;
        url = url.replace("ACCESS_TOKEN", getAccessToken().getToken()).replace("OPENID", openId);
        JSONObject jsonObject = HttpUtil.httpsRequest(url, "GET", null);
        Map<String, String> map = new HashMap<String, String>();
        // 如果请求成功
        if (null != jsonObject) {
            try {
                map.put("nickname", jsonObject.getString("nickname"));
                map.put("province", jsonObject.getString("province"));
                map.put("country", jsonObject.getString("country"));
                map.put("city", jsonObject.getString("city"));
                map.put("sex", jsonObject.getString("sex"));
                map.put("language", jsonObject.getString("language"));
                map.put("headimgurl", jsonObject.getString("headimgurl"));
                map.put("subscribe_time", jsonObject.getString("subscribe_time"));
                map.put("unionid", jsonObject.getString("unionid"));
            } catch (Exception e) {
                logger.error("获取用户信息失败 errcode:{} errmsg:{}", jsonObject.getInteger("errcode"),
                        jsonObject.getString("errmsg"));
                throw new Exception();
            }
        }
        return map;
    }

    /**
     * 发送模板消息
     * 
     * @param mt
     * @return 0表示成功，其他值表示失败
     */
    public static int SendTemplateMessage(MessageTemplate mt) {
        String url = WxConstant.TEMPLATE_MESSAGE_URL;
        url = url.replace("ACCESS_TOKEN", WxUtil.getAccessToken().getToken());
        String jsonMt = JSON.toJSONString(mt);
        JSONObject jsonObject = HttpUtil.httpsRequest(url, "POST", jsonMt);
        int result = 0;
        if (null != jsonObject) {
            if (0 != jsonObject.getInteger("errcode")) {
                result = jsonObject.getInteger("errcode");
                logger.error("发送模板消息失败 errcode:{} errmsg:{}", jsonObject.getInteger("errcode"),
                        jsonObject.getString("errmsg"));
            }
        }
        return result;
    }

    public static byte[] downloadMedia(String mediaId) {
        String url = WxConstant.DOWNLOAD_MEDIA_URL;
        url = url.replace("ACCESS_TOKEN", WxUtil.getAccessToken().getToken()).replace("MEDIA_ID", mediaId);
        HttpURLConnection conn = null;
        try {
            URL u = new URL(url);
            conn = (HttpURLConnection) u.openConnection();
            conn.setDoInput(true);
            conn.setRequestMethod("GET");
            BufferedInputStream bis = new BufferedInputStream(conn.getInputStream());
            ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
            byte[] buff = new byte[1024];
            int rc = 0;
            while ((rc = bis.read(buff, 0, 100)) > 0) {
                swapStream.write(buff, 0, rc);
            }
            return swapStream.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }
        return null;
    }

    public static Map<String, String> makeWXTicket(String url) {
        Map<String, String> map = new HashMap<String, String>();
        String nonce_str = create_nonce_str();
        String timestamp = create_timestamp();
        String string1;
        String signature = "";

        // 注意这里参数名必须全部小写，且必须有序
        string1 = "jsapi_ticket=" + getJsApiTicket().getTicket() + "&noncestr=" + nonce_str + "&timestamp=" + timestamp
                + "&url=" + url;
        try {
            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
            crypt.reset();
            crypt.update(string1.getBytes("UTF-8"));
            signature = byteToHex(crypt.digest());
        } catch (Exception e) {
            logger.error("js api ticket加密失败", e.getMessage());
        }
        map.put("nonceStr", nonce_str);
        map.put("timestamp", timestamp);
        map.put("signature", signature);
        map.put("appId", WxConstant.APP_ID);
        return map;
    }

    private static String byteToHex(final byte[] hash) {
        Formatter formatter = new Formatter();
        for (byte b : hash) {
            formatter.format("%02x", b);
        }
        String result = formatter.toString();
        formatter.close();
        return result;
    }

    private static String create_nonce_str() {
        return UUID.randomUUID().toString();
    }

    private static String create_timestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }
}
